﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.VisualStudio.TeamFoundation.WorkItemTracking;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Collections.Generic;
using Microsoft.VisualStudio.TeamFoundation;
using Microsoft.TeamFoundation.Common;
using System.Windows.Forms;
using System.Collections;
using System.Text;
using Microsoft.TeamFoundation;
using System.Linq;
using System.IO;
using System.Drawing;

namespace TFSExt.ChangeLinkTypes
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideLoadKey("Standard", "1.0", "TFS Productivity Tools - Change Link Types", "Mark Babayev (Experis)", 114)]
    [Guid(GuidList.guidExportWordDocPkgString)]
    public sealed class ChangeLinkTypesPackage : Package
    {
        WorkItemStore wistore;
        IVsTeamExplorer vsTeamExp;
        string AppTitle;
        IVsThreadedWaitDialog2 dlg;
        
        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public ChangeLinkTypesPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overriden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                var menuCommandID = new CommandID(GuidList.guidExportWordDocCmdSet, (int)PkgCmdIDList.cmdidQueryLinkTypes);
                var menuItem = new MenuCommand(QueryLinkTypesCallback, menuCommandID);
                mcs.AddCommand(menuItem);
            }
        }
        #endregion

        private void InitializeVariables()
        {
            var dte = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
            //IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));

            var TFSExt = dte.GetObject("Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt") as TeamFoundationServerExt;

            var tfscoll = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(TFSExt.ActiveProjectContext.DomainUri));
            tfscoll.Connect(Microsoft.TeamFoundation.Framework.Common.ConnectOptions.None);
            tfscoll.EnsureAuthenticated();

            var vcsrv = (VersionControlServer)tfscoll.GetService(typeof(VersionControlServer));
            wistore = tfscoll.GetService<WorkItemStore>();

            AppTitle = Resources.ResourceManager.GetString("AppTitle");

            vsTeamExp = (IVsTeamExplorer)GetService(typeof(IVsTeamExplorer));
        }

        private IVsThreadedWaitDialog2 CreateThreadedWaitDialog(string caption, string message, string progress, string statustext, int total)
        {
            IVsThreadedWaitDialogFactory dialogFactory = GetService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory;
            IVsThreadedWaitDialog2 dlg = null;
            ErrorHandler.ThrowOnFailure(dialogFactory.CreateInstance(out dlg));

            ErrorHandler.ThrowOnFailure(
                dlg.StartWaitDialogWithPercentageProgress(caption, message, progress, null, statustext, true, 0, total, 0));

            return dlg;
        }

        private void QueryLinkTypesCallback(object sender, EventArgs e)
        {
            InitializeVariables();

            IntPtr hier;
            uint itemid;
            IVsMultiItemSelect dummy;
            string canonicalName;
            bool bcanceled;
            int icanceled;
            string OperationCaption = "Change Work Items Link Types";

            var lkdlg = new EditLinkTypeDialog(wistore);
            var dlgResult = lkdlg.ShowDialog();
            if (dlgResult != DialogResult.OK) return;

            var origCursor = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;

            dlg = CreateThreadedWaitDialog(AppTitle, OperationCaption, "Parsing query...", "status", 100);
            dlg.UpdateProgress(OperationCaption, "Parsing query...", "status", 1, 100, true, out bcanceled);

            vsTeamExp.TeamExplorerWindow.GetCurrentSelection(out hier, out itemid, out dummy);
            IVsHierarchy hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(hier);
            Marshal.Release(hier);

            hierarchy.GetCanonicalName(itemid, out canonicalName);
            string[] tokens = canonicalName.Split('/');
            string ProjectName = tokens[1];
            var proj = wistore.Projects[ProjectName];

            QueryItem qItem = proj.QueryHierarchy;

            int currentTokenIndex = 2;
            while (currentTokenIndex < tokens.Length)
            {
                qItem = (qItem as QueryFolder)[tokens[currentTokenIndex]];
                currentTokenIndex++;
            }

            int[] qdata;
            int changedlinks = 0;
            bcanceled = ExecuteQueryLinkTypes(wistore, qItem as QueryDefinition, ProjectName, out qdata);
            dlg.UpdateProgress(OperationCaption, "Changing Link Types...", "status", 1, 100, false, out bcanceled);

            if (!bcanceled) changedlinks = ChangeLinkTypes(qdata, lkdlg.fromlink, lkdlg.tolink);

            dlg.EndWaitDialog(out icanceled);

            Cursor.Current = origCursor;

            if (!bcanceled) MessageBox.Show(changedlinks + " links were changed.", AppTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private int ChangeLinkTypes(int[] qdata, WorkItemLinkType fromlink, WorkItemLinkType tolink)
        {
            int changedlinks = 0;

            foreach(int itm in qdata)
            {
                if (itm == 0) continue;
                
                WorkItem wi = wistore.GetWorkItem(itm);
                foreach (var link in wi.Links.OfType<RelatedLink>().Where(x => x.LinkTypeEnd.LinkType.ReferenceName == fromlink.ReferenceName).ToList())
                {
                    WorkItemLinkTypeEnd linkTypeEnd = wistore.WorkItemLinkTypes.LinkTypeEnds[(link.LinkTypeEnd.IsForwardLink ? tolink.ForwardEnd.Name : tolink.ReverseEnd.Name)];
                    OutputCommandString(string.Format("Updated WorkItemID={0}, OriginalLink={1}, NewLink={2}", wi.Id, link.LinkTypeEnd.Name, linkTypeEnd.Name), AppTitle);

                    if (wi.IsDirty)
                    {
                        try
                        {
                            wi.Save();
                        }
                        catch (Exception ex)
                        {
                            var result = MessageBox.Show(ex.Message, "Failed to save dirty Work Item #" + wi.Id, MessageBoxButtons.AbortRetryIgnore);
                            OutputCommandString(ex.Message, AppTitle);
                            if (result == DialogResult.Abort) return changedlinks;
                            else continue;
                        }
                    }
                    try
                    {
                        wi.Links.Add(new RelatedLink(linkTypeEnd, link.RelatedWorkItemId));
                        wi.Save();
                        changedlinks++;
                    }
                    catch (Exception ex)
                    {
                        var result = MessageBox.Show(ex.Message,"Failed to add new link to Work Item #"+wi.Id,MessageBoxButtons.AbortRetryIgnore);
                        OutputCommandString(ex.Message, AppTitle);
                        if (result == DialogResult.Abort) return changedlinks;
                        else continue;
                    }
                    try
                    {
                        wi.Links.Remove(link);
                        wi.Save();
                    }
                    catch (Exception ex)
                    {
                        var result = MessageBox.Show(ex.Message, "Failed to remove original link from Work Item #" + wi.Id, MessageBoxButtons.AbortRetryIgnore);
                        OutputCommandString(ex.Message, AppTitle);
                        if (result == DialogResult.Abort) return changedlinks;
                        else continue;
                    }
                }
            }

            return changedlinks;
        }

        private bool ExecuteQueryLinkTypes(WorkItemStore wistore, QueryDefinition qdef, string ProjectName, out int[] qdata)
        {
            Hashtable context = new Hashtable();
            StringBuilder strb = new StringBuilder();
            List<int> lqdata = new List<int>();

            context.Add("project", ProjectName); //@me, @today are filled automatically
            var query = new Query(wistore, qdef.QueryText, context);

            if (query.IsLinkQuery)
            {
                foreach (var wilnk in query.RunLinkQuery())
                {
                    lqdata.Add(wilnk.TargetId);
                    lqdata.Add(wilnk.SourceId);

                    OutputCommandString(string.Format("ParentID={0}, WorkItemID={1}", wilnk.SourceId, wilnk.TargetId), AppTitle);
                }
                lqdata = lqdata.Distinct().ToList();
            }
            else
            {
                foreach (WorkItem wi in query.RunQuery())
                {
                    lqdata.Add(wi.Id);

                    OutputCommandString(string.Format("WorkItemID={0}, Title={1}", wi.Id, wi.Title), AppTitle);
                }
            }

            qdata = lqdata.ToArray();

            return false;
        }

        private void OutputCommandString(string text, string caption)
        {
            // --- Build the string to write on the debugger and Output window. 
            var outputText = new StringBuilder();
            outputText.AppendFormat("{0}: {1} ", caption, text);
            outputText.AppendLine();
            // --- Get a reference to IVsOutputWindow. 
            var outputWindow = GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
            if (outputWindow == null) return;

            // --- Get the window pane for the general output. 
            var guidGeneral = Microsoft.VisualStudio.VSConstants.GUID_OutWindowDebugPane;
            IVsOutputWindowPane windowPane;

            if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)))
            {
                return;
            }
            // --- As the last step, write to the output window pane 
            windowPane.OutputString(outputText.ToString());
            windowPane.Activate();
        }

    }
}
